package com.mjf.linkedlist;

/**
 * 约瑟夫问题:
 *      设编号为1，2，… n的n个人围坐一圈，约定编号为k（1<=k<=n）的人从1开始报数，数到m 的那个人出列，
 *      它的下一位又从1开始报数，数到m的那个人又出列，依次类推，直到所有人出列为止，由此产生一个出队编号的序列。
 *
 * 提示：
 *      用一个不带头结点的循环链表来处理Josephu
 *      先构成一个有n个结点的单循环链表，然后由k结点起从1开始计数，计到m时，对应结点从链表中删除，
 *      然后再从被删除结点的下一个结点又从1开始计数，直到最后一个结点从链表中删除算法结束。
 */
public class Josephu {

    public static void main(String[] args) {

        // 测试构建环形链表和遍历
        CircleSingleLinkedList csll = new CircleSingleLinkedList();
        csll.addBoy(5); // 加入小孩
        csll.showBoy(); // 遍历

        // 小孩出圈
        csll.countBoy(1,2,5);   // 2-4-1-5-3
        csll.showBoy(); // 遍历

    }

}


/**
 * 单向环形列表
 */
class CircleSingleLinkedList {

    // 创建一个first节点，当前节点没有编号
    private Boy first = null;

    // 添加小孩节点，构建乘一个环形链表
    public void addBoy(int nums) {
        // 对nums做校验
        if (nums < 1) {
            System.out.println("nums值不正确");
            return;
        }

        Boy curBoy = null;  // 辅助节点，帮助构建环形链表

        // 使用for循环创建环形链表
        for (int i = 1; i <= nums; i++) {
            // 根据编号创建小孩节点
            Boy boy = new Boy(i);

            // 如果是第一个小孩节点
            if (i == 1) {
                first = boy;
                first.setNext(first);
                curBoy = first;     // 让curBoy指向第一个小孩
            } else {
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = curBoy.getNext();
            }

        }

    }

    // 遍历环形链表
    public void showBoy() {
        // 先判断链表是否为空
        if(first == null){
            System.out.println("链表为空");
            return;
        }

        // 因为first节点不能动，我们仍然用一个辅助节点完成遍历
        Boy curBoy = first;

        while(true) {
            System.out.println("小孩的编号为：" + curBoy.getId());
            if(curBoy.getNext() == first) {     // 说明已经遍历完毕
                break;
            }
            curBoy = curBoy.getNext();  // 辅助节点后移
        }

    }

    /**
     * 根据用户的输入，计算出小孩出圈的顺序
     * @param startNo 表示从第几个小孩开始数
     * @param countNum 表示数几下
     * @param nums 表示最初有多少个小孩在圈中
     */
    public void countBoy(int startNo, int countNum, int nums) {
        // 先对数据进行校验
        if(first == null || startNo < 1 || startNo > nums) {
            System.out.println("参数输入有误，请重新输入");
            return;
        }

        Boy helper = first;  // 创建辅助指针，帮助完成小孩出圈
        // 事先应该让helper指向环形链表的最后节点
        while (helper.getNext() != first) {
            helper = helper.getNext();
        }

        // 当小孩报数前，先让first和helper指针同时移动 startNo-1 次
        for (int i = 0; i < startNo - 1; i++) {
            first = first.getNext();
            helper = helper.getNext();
        }

        // 当小孩报数时，让first和helper指针同时移动 countNum-1 次
        // 说明圈中只剩一个人
        while (helper != first) {      // 循环到圈中只剩一个人
            for (int i = 0; i < countNum - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            // 这时first指向的节点，就是要出圈的小孩节点
            System.out.printf("小孩%d出圈\n", first.getId());
            // 这时将first指向的小孩出圈
            first = first.getNext();    // first节点后移
            helper.setNext(first);  // 将原先的first节点出圈
        }

        // 将最后一个小孩出圈
        System.out.printf("小孩%d出圈\n", first.getId());
        first = null;
    }

}


/**
 * 创建一个Boy类，表示一个节点
 */
class Boy {
    private int id; // 编号
    private Boy next;   // 指向下一个节点，默认为null

    public Boy(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }
}